home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / js / calWcapCalendarItems.js < prev    next >
Encoding:
JavaScript  |  2007-09-20  |  59.5 KB  |  1,508 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Sun Microsystems code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * Sun Microsystems, Inc.
  19.  * Portions created by the Initial Developer are Copyright (C) 2007
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Daniel Boelzle <daniel.boelzle@sun.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. var calWcapCalendar;
  40. if (!calWcapCalendar) {
  41.     calWcapCalendar = {};
  42.     calWcapCalendar.prototype = {};
  43. }
  44.  
  45. calWcapCalendar.prototype.encodeAttendee =
  46. function calWcapCalendar_encodeAttendee(att)
  47. {
  48.     if (LOG_LEVEL > 2)
  49.         log("attendee.icalProperty.icalString=" + att.icalProperty.icalString, this);
  50.     function encodeAttr(val, attr, params) {
  51.         if (val && val.length > 0) {
  52.             if (params.length > 0)
  53.                 params += "^";
  54.             if (attr)
  55.                 params += (attr + "=");
  56.             params += encodeURIComponent(val);
  57.         }
  58.         return params;
  59.     }
  60.     var params = encodeAttr(att.rsvp ? "TRUE" : "FALSE", "RSVP", "");
  61.     params = encodeAttr(att.participationStatus, "PARTSTAT", params);
  62.     params = encodeAttr(att.role, "ROLE", params);
  63.     params = encodeAttr(att.commonName, "CN", params);
  64.     return encodeAttr(att.id, null, params);
  65. };
  66.  
  67. calWcapCalendar.prototype.encodeNscpTzid =
  68. function calWcapCalendar_encodeNscpTzid(dateTime)
  69. {
  70.     var params = "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-";
  71.     if (!dateTime || !dateTime.isValid || dateTime.isDate || (dateTime.timezone == "floating")) {
  72.         params += "DELETE^";
  73.     } else {
  74.         params += ("REPLACE^" + encodeURIComponent(this.getAlignedTimezone(dateTime.timezone)));
  75.     }
  76.     return params;
  77. };
  78.  
  79. calWcapCalendar.prototype.getRecurrenceParams =
  80. function calWcapCalendar_getRecurrenceParams(
  81.     item, out_rrules, out_rdates, out_exrules, out_exdates)
  82. {
  83.     // recurrences:
  84.     out_rrules.value = [];
  85.     out_rdates.value = [];
  86.     out_exrules.value = [];
  87.     out_exdates.value = [];
  88.     if (item.recurrenceInfo) {
  89.         var rItems = item.recurrenceInfo.getRecurrenceItems({});
  90.         for each (var rItem in rItems) {
  91.             var isNeg = rItem.isNegative;
  92.             if (rItem instanceof Components.interfaces.calIRecurrenceRule) {
  93.                 var rule = ("\"" + encodeURIComponent(
  94.                                 rItem.icalProperty.valueAsIcalString) +
  95.                             "\"");
  96.                 if (isNeg)
  97.                     out_exrules.value.push(rule);
  98.                 else
  99.                     out_rrules.value.push(rule);
  100.             }
  101.             else if (rItem instanceof Components.interfaces.calIRecurrenceDateSet) {
  102.                 var d = rItem.getDates({});
  103.                 for each (var d in rdates) {
  104.                     if (isNeg)
  105.                         out_exdates.value.push( getIcalUTC(d.date) );
  106.                     else
  107.                         out_rdates.value.push( getIcalUTC(d.date) );
  108.                 }
  109.             }
  110.             else if (rItem instanceof Components.interfaces.calIRecurrenceDate) {
  111.                 if (isNeg)
  112.                     out_exdates.value.push( getIcalUTC(rItem.date) );
  113.                 else
  114.                     out_rdates.value.push( getIcalUTC(rItem.date) );
  115.             }
  116.             else {
  117.                 this.notifyError("don\'t know how to handle this recurrence item: " +
  118.                                  rItem.valueAsIcalString);
  119.             }
  120.         }
  121.     }
  122. };
  123.  
  124. calWcapCalendar.prototype.encodeRecurrenceParams =
  125. function calWcapCalendar_encodeRecurrenceParams(item, oldItem)
  126. {
  127.     var rrules = {};
  128.     var rdates = {};
  129.     var exrules = {};
  130.     var exdates = {};
  131.     this.getRecurrenceParams(item, rrules, rdates, exrules, exdates);
  132.     if (oldItem) {
  133.         // actually only write changes if an old item has been changed, because
  134.         // cs recreates the whole series if a rule has changed.
  135.         // xxx todo: one problem is left when a master only holds EXDATEs,
  136.         //           and effectively no item is present anymore.
  137.         //           cs seems not to clean up automatically, but it does when
  138.         //           when deleting an occurrence {id, rec-id}!
  139.         //           So this still leaves the question open why deleteOccurrence
  140.         //           does not directly call deleteItem rather than modifyItem,
  141.         //           which leads to a much cleaner usage.
  142.         //           I assume this mimic has been chosen for easier undo/redo
  143.         //           support (Undo would then have to distinguish whether
  144.         //           it has previously deleted an occurrence or ordinary item:
  145.         //            - entering an exception again
  146.         //            - or adding an item)
  147.         //           Currently it can just modifyItem(newItem/oldItem) back.
  148.         var rrules_ = {};
  149.         var rdates_ = {};
  150.         var exrules_ = {};
  151.         var exdates_ = {};
  152.         this.getRecurrenceParams(oldItem, rrules_, rdates_, exrules_, exdates_);
  153.         
  154.         function sameSet(list, list_) {
  155.             return (list.length == list_.length &&
  156.                     list.every( function everyFunc(x) {
  157.                                     return list_.some(
  158.                                         function someFunc(y) { return x == y; } );
  159.                                 }
  160.                         ));
  161.         }
  162.         if (sameSet(rrules.value, rrules_.value))
  163.             rrules.value = null; // don't write
  164.         if (sameSet(rdates.value, rdates_.value))
  165.             rdates.value = null; // don't write
  166.         if (sameSet(exrules.value, exrules.value))
  167.             exrules.value = null; // don't write
  168.         if (sameSet(exdates.value, exdates_.value))
  169.             exdates.value = null; // don't write
  170.     }
  171.     
  172.     function encodeList(list) {
  173.         var ret = "";
  174.         for each ( var str in list ) {
  175.             if (ret.length > 0)
  176.                 ret += ";";
  177.             ret += str;
  178.         }
  179.         return ret;
  180.     }
  181.     var ret = "";
  182.     if (rrules.value)
  183.         ret += ("&rrules=" + encodeList(rrules.value));
  184.     if (rdates.value)
  185.         ret += ("&rdates=" + encodeList(rdates.value));
  186.     if (exrules.value)
  187.         ret += ("&exrules=" + encodeList(exrules.value));
  188.     if (exdates.value)
  189.         ret += ("&exdates=" + encodeList(exdates.value));
  190.     return ret;
  191.     // xxx todo:
  192.     // rchange=1: expand recurrences,
  193.     // or whether to replace the rrule, ambiguous documentation!!!
  194.     // check with store(with no uid) upon adoptItem() which behaves strange
  195.     // if rchange=0 is set!
  196. };
  197.  
  198. calWcapCalendar.prototype.getAlarmParams =
  199. function calWcapCalendar_getAlarmParams(item)
  200. {
  201.     var params = null;
  202.     var alarmStart = item.alarmOffset;
  203.     if (alarmStart) {
  204.         if (item.alarmRelated == calIItemBase.ALARM_RELATED_END) {
  205.             // cs does not support explicit RELATED=END when
  206.             // both start|entry and end|due are written
  207.             var dur = item.duration;
  208.             if (dur) { // both given
  209.                 alarmStart = alarmStart.clone();
  210.                 alarmStart.addDuration(dur);
  211.             } // else only end|due is set, alarm makes little sense though
  212.         }
  213.         
  214.         var emails = "";
  215.         if (item.hasProperty("alarmEmailAddress"))
  216.             emails = encodeURIComponent(item.getProperty("alarmEmailAddress"));
  217.         else {
  218.             this.session.getDefaultAlarmEmails({}).forEach(
  219.                 function forEachFunc(email) {
  220.                     if (emails.length > 0)
  221.                         emails += ";";
  222.                     emails += encodeURIComponent(email);
  223.                 });
  224.         }
  225.         if (emails.length > 0) {
  226.             params = ("&alarmStart=" + alarmStart.icalString);
  227.             params += ("&alarmEmails=" + emails);
  228.         }
  229.         // else popup
  230.     }
  231.     if (!params) // clear popup, email alarm:
  232.         params = "&alarmStart=&alarmPopup=&alarmEmails=";
  233.     return params;
  234. };
  235.  
  236. // why ever, X-S1CS-EMAIL is unsupported though documented
  237. // for get_calprops... WTF.
  238. function getCalId(att) {
  239.     return (att ? att.getProperty("X-S1CS-CALID") : null);
  240. }
  241.  
  242. function getAttendeeByCalId(atts, calId) {
  243.     for each (var att in atts) {
  244.         if (getCalId(att) == calId)
  245.             return att;
  246.     }
  247.     return null;
  248. }
  249.  
  250. calWcapCalendar.prototype.isInvitation =
  251. function calWcapCalendar_isInvitation(item)
  252. {
  253.     if (!this.session.isLoggedIn)
  254.         return false; // don't know
  255.     var orgCalId = getCalId(item.organizer);
  256.     if (!orgCalId)
  257.         return false;
  258.     var calId = this.calId;
  259.     if (orgCalId == calId)
  260.         return false;
  261.     return (getAttendeeByCalId(item.getAttendees({}), calId) != null);
  262. };
  263.  
  264. calWcapCalendar.prototype.getInvitedAttendee =
  265. function calWcapCalendar_getInvitedAttendee(item)
  266. {
  267.     return getAttendeeByCalId(item.getAttendees({}), this.calId);
  268. };
  269.  
  270. function equalDatetimes(one, two) {
  271.     return ((!one && !two) ||
  272.             (one && two &&
  273.              (one.isDate == two.isDate) &&
  274.              (one.compare(two) == 0)));
  275. }
  276.  
  277. function identicalDatetimes(one, two) {
  278.     return ((!one && !two) ||
  279.             (equalDatetimes(one, two) &&
  280.              (one.timezone == two.timezone)));
  281. }
  282.  
  283. // @return null if nothing has changed else value to be written
  284. function diffProperty(newItem, oldItem, propName) {
  285.     var val = newItem.getProperty(propName);
  286.     var oldVal = (oldItem ? oldItem.getProperty(propName) : null);
  287.     if (val === null) {
  288.         // force being set when - no old item, eg when adding new item
  289.         //                      - property is to be deleted
  290.         if (!oldItem || oldVal)
  291.             val = "";
  292.     }
  293.     else {
  294.         val = val.replace(/(\r\n)|\n/g, "\r\n");
  295.         if (oldVal)
  296.             oldVal = oldVal.replace(/(\r\n)|\n/g, "\r\n");
  297.         if (val == oldVal)
  298.             val = null;
  299.     }
  300.     return val;
  301. }
  302.  
  303. const METHOD_PUBLISH = 1;
  304. const METHOD_REQUEST = 2;
  305. const METHOD_REPLY   = 4;
  306. const METHOD_CANCEL  = 8;
  307. const METHOD_UPDATE  = 256;
  308.  
  309. calWcapCalendar.prototype.storeItem =
  310. function calWcapCalendar_storeItem(bAddItem, item, oldItem, request, netRespFunc)
  311. {
  312.     var this_ = this;
  313.     var bIsEvent = isEvent(item);
  314.     var bIsParent = isParent(item);
  315.     
  316.     var method = METHOD_PUBLISH;
  317.     var bNoSmtpNotify = false;
  318.     var params = "";
  319.     
  320.     var calId = this.calId;
  321.     if (this.isInvitation(item)) { // REPLY
  322.         method = METHOD_REPLY;
  323.         var att = getAttendeeByCalId(item.getAttendees({}), calId);
  324.         if (att) {
  325.             log("attendee: " + att.icalProperty.icalString, this);
  326.             var oldAtt = null;
  327.             if (oldItem)
  328.                 oldAtt = getAttendeeByCalId(oldItem.getAttendees({}), calId);
  329.             if (!oldAtt || (att.participationStatus != oldAtt.participationStatus)) {
  330.                 // REPLY first for just this calendar:
  331.                 params += ("&attendees=PARTSTAT=" + att.participationStatus +
  332.                            "^" + encodeURIComponent(att.id));
  333.             }
  334.         }
  335.     }
  336.     else { // PUBLISH, REQUEST
  337.         
  338.         // workarounds for server bugs concerning recurrences/exceptions:
  339.         // - if first occurrence is an exception
  340.         //   and an EXDATE for that occurrence ought to be written,
  341.         //   then the master item's data is replaced with that EXDATEd exception. WTF.
  342.         // - if start/end date is being written on master, the previously EXDATEd
  343.         //   exception overwrites master, why ever.
  344.         // So in these cases: write all data of master.
  345.         
  346.         var bIsAllDay = false;
  347.         if (bIsEvent) {
  348.             var dtstart = item.startDate;
  349.             var dtend = item.endDate;
  350.             bIsAllDay = (dtstart.isDate && dtend.isDate);
  351.             if (!oldItem || !identicalDatetimes(dtstart, oldItem.startDate)
  352.                          || !identicalDatetimes(dtend, oldItem.endDate)) {
  353.                 params += ("&dtstart=" + getIcalUTC(dtstart));
  354.                 params += ("&X-NSCP-DTSTART-TZID=" + this.encodeNscpTzid(dtstart));
  355.                 params += ("&dtend=" + getIcalUTC(dtend));
  356.                 params += ("&X-NSCP-DTEND-TZID=" + this.encodeNscpTzid(dtend));
  357.                 params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0");
  358.                 
  359.                 if (bIsParent && item.recurrenceInfo)
  360.                     oldItem = null; // recurrence/exceptions hack: write whole master
  361.             }
  362.         }
  363.         else { // calITodo
  364.             // xxx todo: dtstart is mandatory for cs, so if this is
  365.             //           undefined, assume an allDay todo???
  366.             var dtstart = item.entryDate;
  367.             var dtend = item.dueDate;
  368.  
  369.             // cs bug: enforce DUE (set to DTSTART) if alarm is set
  370.             if (!dtend && item.alarmOffset) {
  371.                 dtend = dtstart;
  372.             }
  373.             
  374.             bIsAllDay = (dtstart && dtstart.isDate);
  375.             if (!oldItem || !identicalDatetimes(dtstart, oldItem.entryDate)
  376.                          || !identicalDatetimes(dtend, oldItem.dueDate)) {
  377.                 params += ("&dtstart=" + getIcalUTC(dtstart));
  378.                 params += ("&X-NSCP-DTSTART-TZID=" + this.encodeNscpTzid(dtstart));
  379.                 params += ("&due=" + getIcalUTC(dtend));
  380.                 params += ("&X-NSCP-DUE-TZID=" + this.encodeNscpTzid(dtend));
  381.                 params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0");
  382.                 
  383.                 if (bIsParent && item.recurrenceInfo)
  384.                     oldItem = null; // recurrence/exceptions hack: write whole master
  385.             }
  386.         }
  387.         if (bIsParent) {
  388.             var recParams = this.encodeRecurrenceParams(item, oldItem);
  389.             if (recParams.length > 0) {
  390.                 oldItem = null; // recurrence/exceptions hack: write whole master
  391.                 params += recParams;
  392.             }
  393.         }
  394.         
  395.         function getOrgId(item) {
  396.             return (item && item.organizer && item.organizer.id ? item.organizer.id : null);
  397.         }
  398.         var orgCalId = getCalId(item.organizer);
  399.         // xxx todo: mbu initially sets this ownerId:
  400.         if (!orgCalId) {
  401.             var orgId = getOrgId(item);
  402.             if (!orgId || (orgId == this.ownerId))
  403.                 orgCalId = calId; // patch to this calid
  404.         }
  405.         
  406.         var attendees = item.getAttendees({});
  407.         if (attendees.length > 0) {
  408.             // xxx todo: why ever, X-S1CS-EMAIL is unsupported though documented for calprops... WTF.
  409.             function encodeAttendees(atts) {
  410.                 function attendeeSort(one, two) {
  411.                     one = one.id;
  412.                     two = two.id;
  413.                     if (one == two)
  414.                         return 0;
  415.                     return (one < two ? -1 : 1);
  416.                 }
  417.                 atts = atts.concat([]);
  418.                 atts.sort(attendeeSort);
  419.                 var ret = "";
  420.                 for (var i = 0; i < atts.length; ++i) {
  421.                     if (ret.length > 0)
  422.                         ret += ";";
  423.                     ret += this_.encodeAttendee(atts[i]);
  424.                 }
  425.                 return ret;
  426.             }
  427.             var attParam = encodeAttendees(attendees);
  428.             if (!oldItem || attParam != encodeAttendees(oldItem.getAttendees({}))) {
  429.                 params += ("&attendees=" + attParam);
  430.             }
  431.             
  432.             if (orgCalId == calId)
  433.                 method = METHOD_REQUEST;
  434.             else {
  435.                 method = METHOD_UPDATE;
  436.                 bNoSmtpNotify = true;
  437.             }
  438.         } // else using just PUBLISH
  439.         else if (oldItem && oldItem.getAttendees({}).length > 0) {
  440.             params += "&attendees="; // clear attendees
  441.         }
  442.         
  443.         if (orgCalId) {
  444.             if (!oldItem || (orgCalId != getCalId(oldItem.organizer)))
  445.                 params += ("&orgCalid=" + encodeURIComponent(orgCalId));
  446.         }
  447.         else { // might be a copy of an iTIP invitation:
  448.             var orgEmail = getOrgId(item);
  449.             if (!oldItem || (getOrgId(oldItem) != orgEmail))
  450.                 params += ("&orgEmail=" + encodeURIComponent(orgEmail));
  451.         }
  452.         
  453.         var val = item.title;
  454.         if (!oldItem || val != oldItem.title)
  455.             params += ("&summary=" + encodeURIComponent(val));
  456.         // xxx todo: missing relatedTos= in cal api
  457.         val = diffProperty(item, oldItem, "CATEGORIES");
  458.         if (val !== null) // xxx todo: check whether ;-separated:
  459.             params += ("&categories=" + encodeURIComponent( val.replace(/,/g, ";") ));
  460.         val = diffProperty(item, oldItem, "DESCRIPTION");
  461.         if (val !== null)
  462.             params += ("&desc=" + encodeURIComponent(val));
  463.         val = diffProperty(item, oldItem, "LOCATION");
  464.         if (val !== null)
  465.             params += ("&location=" + encodeURIComponent(val));
  466.         val = diffProperty(item, oldItem, "URL");
  467.         if (val !== null)
  468.             params += ("&icsUrl=" + encodeURIComponent(val));
  469.         // xxx todo: default prio is 0 (5 in sjs cs)
  470.         val = item.priority;
  471.         if (!oldItem || val != oldItem.priority)
  472.             params += ("&priority=" + encodeURIComponent(val));
  473.         
  474.         function getPrivacy(item) {
  475.             return ((item.privacy && item.privacy != "") ? item.privacy : "PUBLIC");
  476.         }
  477.         var icsClass = getPrivacy(item);
  478.         if (!oldItem || icsClass != getPrivacy(oldItem))
  479.             params += ("&icsClass="+ icsClass);
  480.         
  481.         if (!oldItem || item.status != oldItem.status) {
  482.             switch (item.status) {
  483.             case "CONFIRMED":    params += "&status=0"; break;
  484.             case "CANCELLED":    params += "&status=1"; break;
  485.             case "TENTATIVE":    params += "&status=2"; break;
  486.             case "NEEDS-ACTION": params += "&status=3"; break;
  487.             case "COMPLETED":    params += "&status=4"; break;
  488.             case "IN-PROCESS":   params += "&status=5"; break;
  489.             case "DRAFT":        params += "&status=6"; break;
  490.             case "FINAL":        params += "&status=7"; break;
  491.             default:
  492.                 params += "&status=3"; // NEEDS-ACTION
  493.                 break;
  494.             }
  495.         }
  496.         
  497.         val = diffProperty(item, oldItem, "TRANSP");
  498.         if (val !== null) {
  499.             switch (val) {
  500.             case "TRANSPARENT":
  501.                 params += "&transparent=1";
  502.                 break;
  503.             case "OPAQUE":
  504.                 params += "&transparent=0";
  505.                 break;
  506.             default:
  507.                 params += ("&transparent=" +
  508.                            (((icsClass == "PRIVATE") || bIsAllDay) ? "1" : "0"));
  509.                 break;
  510.             }
  511.         }
  512.         
  513.         if (!bIsEvent) {
  514.             if (!oldItem || item.percentComplete != oldItem.percentComplete)
  515.                 params += ("&percent=" + item.percentComplete.toString(10));
  516.             if (!oldItem || !equalDatetimes(item.completedDate, oldItem.completedDate))
  517.                 params += ("&completed=" + getIcalUTC(item.completedDate));
  518.         }
  519.         
  520.         // attachment urls:
  521.         function getAttachments(item) {
  522.             var ret = "";
  523.             var attachments = item.attachments;
  524.             if (attachments) {
  525.                 var strings = [];
  526.                 for each (var att in attachements) {
  527.                     if (typeof(att) == "string")
  528.                         strings.push(encodeURIComponent(att));
  529.                     else if (att instanceof Components.interfaces.calIAttachment)
  530.                         strings.push(encodeURIComponent(att.uri.spec));
  531.                     else // xxx todo
  532.                         logError("only URLs supported as attachment, not: " + att, this_);
  533.                 }
  534.                 strings.sort();
  535.                 for (var i = 0; i < strings.length; ++i) {
  536.                     if (i > 0)
  537.                         ret += ";";
  538.                     ret += strings[i];
  539.                 }
  540.             }
  541.             return ret;
  542.         }
  543.         var val = getAttachments(item);
  544.         if (!oldItem || val != getAttachments(oldItem))
  545.             params += ("&attachments=" + val);        
  546.     } // PUBLISH, REQUEST
  547.     
  548.     var alarmParams = this.getAlarmParams(item);
  549.     if (!oldItem || (this.getAlarmParams(oldItem) != alarmParams)) {
  550.         if ((method == METHOD_REQUEST) && params.length == 0) {
  551.             // assure no email notifications about this change:
  552.             bNoSmtpNotify = true;
  553.         }
  554.         params += alarmParams;
  555.     }
  556.     
  557.     if (params.length == 0) {
  558.         log("no change at all.", this);
  559.         if (LOG_LEVEL > 2) {
  560.             log("old item:\n" + oldItem.icalString + "\n\nnew item:\n" + item.icalString, this);
  561.         }
  562.         request.execRespFunc(null, item);
  563.     }
  564.     else {
  565.         var someDate = (item.startDate || item.entryDate || item.dueDate);
  566.         if (someDate) {
  567.             // provide some date: eMail notification dates are influenced by this parameter...
  568.             params += ("&tzid=" + encodeURIComponent(
  569.                            this.getAlignedTimezone(someDate.timezone)));
  570.         }
  571.         
  572.         if (item.id)
  573.             params += ("&uid=" + encodeURIComponent(item.id));
  574.         
  575.         // be picky about create/modify:
  576.         // WCAP_STORE_TYPE_CREATE, WCAP_STORE_TYPE_MODIFY
  577.         params += (bAddItem ? "&storetype=1" : "&storetype=2");
  578.         
  579.         if (bIsParent)
  580.             params += "&mod=4"; // THIS AND ALL INSTANCES
  581.         else {
  582.             var rid = item.recurrenceId;
  583.             if (rid.isDate) {
  584.                 // cs does not accept DATE:
  585.                 rid = rid.clone();
  586.                 rid.isDate = false;
  587.             }
  588.             params += ("&mod=1&rid=" + getIcalUTC(rid)); // THIS INSTANCE
  589.         }
  590.         
  591.         params += ("&method=" + method);
  592.         if (bNoSmtpNotify) {
  593.             params += "&smtp=0&smtpNotify=0¬ify=0";
  594.         }
  595.         params += "&replace=1"; // (update) don't append to any lists    
  596.         params += "&fetch=1&relativealarm=1&compressed=1&recurring=1";
  597.         params += "&emailorcalid=1&fmt-out=text%2Fcalendar";
  598.         
  599.         this.issueNetworkRequest(request, netRespFunc, stringToIcal,
  600.                                  bIsEvent ? "storeevents" : "storetodos", params,
  601.                                  calIWcapCalendar.AC_COMP_READ |
  602.                                  calIWcapCalendar.AC_COMP_WRITE);
  603.     }
  604. };
  605.  
  606. calWcapCalendar.prototype.tunnelXProps =
  607. function calWcapCalendar_tunnelXProps(destItem, srcItem)
  608. {
  609.     // xxx todo: temp workaround for bug in calItemBase.js
  610.     if (!isParent(srcItem))
  611.         return;
  612.     // tunnel alarm X-MOZ-SNOOZE only if alarm is still set:
  613.     var alarmOffset = destItem.alarmOffset;
  614.     var enumerator = srcItem.propertyEnumerator;
  615.     while (enumerator.hasMoreElements()) {
  616.         try {
  617.             var prop = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
  618.             var name = prop.name;
  619.             if (name.indexOf("X-MOZ-") == 0) {
  620.                 switch (name) {
  621.                 // keep snooze stamps for occurrences only and if alarm is still set:
  622.                 case "X-MOZ-SNOOZE-TIME":
  623.                     if (!alarmOffset)
  624.                         break; // alarm has been reset
  625.                     // fallthru intended:
  626.                 default:
  627.                     if (LOG_LEVEL > 1)
  628.                         log("tunneling " + name + "=" + prop.value, this);
  629.                     destItem.setProperty(name, prop.value);
  630.                     break;
  631.                 }
  632.             }
  633.         }
  634.         catch (exc) {
  635.             logError(exc, this);
  636.         }
  637.     }
  638. };
  639.  
  640. calWcapCalendar.prototype.adoptItem =
  641. function calWcapCalendar_adoptItem(item, listener)
  642. {
  643.     var this_ = this;
  644.     var request = new calWcapRequest(
  645.         function adoptItem_resp(request, err, newItem) {
  646.             if (listener) {
  647.                 listener.onOperationComplete(this_.superCalendar,
  648.                                              getResultCode(err),
  649.                                              calIOperationListener.ADD,
  650.                                              err ? item.id : newItem.id,
  651.                                              err ? err : newItem);
  652.             }
  653.             if (err)
  654.                 this_.notifyError(err);
  655.             else
  656.                 this_.notifyObservers("onAddItem", [newItem]);
  657.         },
  658.         log("adoptItem() call: " + item.title, this));
  659.     
  660.     try {
  661.         if (!isParent(item)) {
  662.             this_.logError("adoptItem(): unexpected proxy!");
  663.             debugger;
  664.         }
  665.         this.storeItem(true/*bAddItem*/,
  666.                        item, null, request,
  667.                        function netResp(err, icalRootComp) {
  668.                            if (err)
  669.                                throw err;
  670.                            var items = this_.parseItems(
  671.                                icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS,
  672.                                0, null, null, true /* bLeaveMutable */);
  673.                            if (items.length < 1)
  674.                                throw new Components.Exception("empty VCALENDAR returned!");
  675.                            if (items.length > 1)
  676.                                this_.notifyError("unexpected number of items: " + items.length);
  677.                            var newItem = items[0];
  678.                            this_.tunnelXProps(newItem, item);
  679.                            item.makeImmutable();
  680.                            // invalidate cached results:
  681.                            delete this_.m_cachedResults;
  682.                            log("newItem.id=" + newItem.id, this_);
  683.                            // xxx todo: may log request status
  684.                            request.execRespFunc(null, newItem);
  685.                        });
  686.     }
  687.     catch (exc) {
  688.         request.execRespFunc(exc);
  689.     }
  690.     return request;
  691. }
  692.  
  693. calWcapCalendar.prototype.addItem =
  694. function calWcapCalendar_addItem(item, listener)
  695. {
  696.     this.adoptItem(item.clone(), listener);
  697. };
  698.  
  699. calWcapCalendar.prototype.modifyItem =
  700. function calWcapCalendar_modifyItem(newItem, oldItem, listener)
  701. {
  702.     var this_ = this;
  703.     var request = new calWcapRequest(
  704.         function modifyItem_resp(request, err, item) {
  705.             if (listener) {
  706.                 listener.onOperationComplete(
  707.                     this_.superCalendar, getResultCode(err),
  708.                     calIOperationListener.MODIFY,
  709.                     newItem.id, err ? err : item);
  710.             }
  711.             if (err)
  712.                 this_.notifyError(err);
  713.             else
  714.                 this_.notifyObservers("onModifyItem", [item, oldItem]);
  715.         },
  716.         log("modifyItem() call: " + newItem.id, this));
  717.     
  718.     try {
  719.         if (!newItem.id)
  720.             throw new Components.Exception("new item has no id!");
  721.         var oldItem_ = oldItem;
  722.         if (oldItem && !isParent(newItem) &&
  723.             !oldItem.parentItem.recurrenceInfo.getExceptionFor(newItem.recurrenceId, false)) {
  724.             // pass null for oldItem when creating new exceptions, write whole item:
  725.             oldItem_ = null;
  726.         }
  727.         this.storeItem(false/*bAddItem*/,
  728.                        newItem, oldItem_, request,
  729.                        function netResp(err, icalRootComp) {
  730.                            if (err)
  731.                                throw err;
  732.                            var items = this_.parseItems(
  733.                                icalRootComp,
  734.                                calICalendar.ITEM_FILTER_ALL_ITEMS,
  735.                                0, null, null, true /* bLeaveMutable */);
  736.                            if (items.length < 1)
  737.                                throw new Components.Exception("empty VCALENDAR returned!");
  738.                            if (items.length > 1)
  739.                                this_.notifyError("unexpected number of items: " + items.length);
  740.                            var item = items[0];
  741.                            this_.tunnelXProps(item, newItem);
  742.                            item.makeImmutable();
  743.                            // invalidate cached results:
  744.                            delete this_.m_cachedResults;
  745.                            // xxx todo: maybe log request status
  746.                            request.execRespFunc(null, item);
  747.                        });
  748.     }
  749.     catch (exc) {
  750.         request.execRespFunc(exc);
  751.     }
  752.     return request;
  753. };
  754.  
  755. calWcapCalendar.prototype.deleteItem =
  756. function calWcapCalendar_deleteItem(item, listener)
  757. {
  758.     var this_ = this;
  759.     var request = new calWcapRequest(
  760.         function deleteItem_resp(request, err) {
  761.             // xxx todo: need to notify about each deleted item if multiple?
  762.             if (listener) {
  763.                 listener.onOperationComplete(
  764.                     this_.superCalendar, getResultCode(err),
  765.                     calIOperationListener.DELETE,
  766.                     item.id, err ? err : item);
  767.             }
  768.             if (err)
  769.                 this_.notifyError(err);
  770.             else
  771.                 this_.notifyObservers("onDeleteItem", [item]);
  772.         },
  773.         log("deleteItem() call: " + item.id, this));
  774.     
  775.     try {
  776.         if (!item.id)
  777.             throw new Components.Exception("no item id!");
  778.         var params = ("&uid=" + encodeURIComponent(item.id));
  779.         if (isParent(item)) // delete THIS AND ALL:
  780.             params += "&mod=4&rid=0";
  781.         else { // delete THIS INSTANCE:
  782.             var rid = item.recurrenceId;
  783.             if (rid.isDate) {
  784.                 // cs does not accept DATE:
  785.                 rid = rid.clone();
  786.                 rid.isDate = false;
  787.             }
  788.             params += ("&mod=1&rid=" + getIcalUTC(rid));
  789.         }
  790.         
  791.         var orgCalId = getCalId(item.organizer);
  792.         if (!orgCalId || (orgCalId != this.calId)) {
  793.             // item does not belong to this user, so son't notify:
  794.             params += "&smtp=0&smtpNotify=0¬ify=0";
  795.         }
  796.         
  797.         params += "&fmt-out=text%2Fxml";
  798.         
  799.         this.issueNetworkRequest(
  800.             request,
  801.             function netResp(err, xml) {
  802.                 if (err)
  803.                     throw err;
  804.                 // invalidate cached results:
  805.                 delete this_.m_cachedResults;
  806.                 if (LOG_LEVEL > 0)
  807.                     log("deleteItem(): " + getWcapRequestStatusString(xml), this_);
  808.             },
  809.             stringToXml, isEvent(item) ? "deleteevents_by_id" : "deletetodos_by_id",
  810.             params, calIWcapCalendar.AC_COMP_WRITE);
  811.     }
  812.     catch (exc) {
  813.         request.execRespFunc(exc);
  814.     }
  815.     return request;
  816. };
  817.  
  818. calWcapCalendar.prototype.parseItems = function calWcapCalendar_parseItems(
  819.     icalRootComp, itemFilter, maxResults, rangeStart, rangeEnd, bLeaveMutable)
  820. {
  821.     var items = [];
  822.     var unexpandedItems = [];
  823.     var uid2parent = {};
  824.     var excItems = [];
  825.     
  826.     var componentType = "ANY";
  827.     switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) {
  828.     case calICalendar.ITEM_FILTER_TYPE_TODO:
  829.         componentType = "VTODO"; break;
  830.     case calICalendar.ITEM_FILTER_TYPE_EVENT:
  831.         componentType = "VEVENT"; break;
  832.     }
  833.     
  834.     var this_ = this;
  835.     forEachIcalComponent(
  836.         icalRootComp, componentType,
  837.         function( subComp )
  838.         {
  839.             function patchTimezone(subComp, attr, xprop) {
  840.                 var dt = subComp[attr];
  841.                 if (dt) {
  842.                     if (LOG_LEVEL > 2) {
  843.                         log(attr + " is " + dt, this_);
  844.                     }
  845.                     var tzid = subComp.getFirstProperty(xprop);
  846.                     if (tzid != null) {
  847.                         subComp[attr] = dt.getInTimezone(tzid.value);
  848.                         if (LOG_LEVEL > 2) {
  849.                             log("patching " + xprop + " from " +
  850.                                 dt + " to " + subComp[attr], this_);
  851.                         }
  852.                     }
  853.                 }
  854.             }
  855.  
  856.             patchTimezone(subComp, "startTime", "X-NSCP-DTSTART-TZID");
  857.             var item = null;
  858.             switch (subComp.componentType) {
  859.             case "VEVENT": {
  860.                 patchTimezone(subComp, "endTime", "X-NSCP-DTEND-TZID");
  861.                 item = new CalEvent();
  862.                 item.icalComponent = subComp;
  863.                 break;
  864.             }
  865.             case "VTODO": {
  866.                 patchTimezone(subComp, "dueTime", "X-NSCP-DUE-TZID");
  867.                 item = new CalTodo();
  868.                 item.icalComponent = subComp;
  869.                 switch (itemFilter & calICalendar.ITEM_FILTER_COMPLETED_ALL) {
  870.                     case calICalendar.ITEM_FILTER_COMPLETED_YES:
  871.                         if (!item.isCompleted) {
  872.                             delete item;
  873.                             item = null;
  874.                         }
  875.                     break;
  876.                     case calICalendar.ITEM_FILTER_COMPLETED_NO:
  877.                         if (item.isCompleted) {
  878.                             delete item;
  879.                             item = null;
  880.                         }
  881.                     break;
  882.                 }
  883.                 break;
  884.             }
  885.             }
  886.             if (item) {
  887.                 if (!item.title) {
  888.                     // assumed to look at a subscribed calendar,
  889.                     // so patch title for private items:
  890.                     switch (item.privacy) {
  891.                     case "PRIVATE":
  892.                         item.title = g_privateItemTitle;
  893.                         break;
  894.                     case "CONFIDENTIAL":
  895.                         item.title = g_confidentialItemTitle;
  896.                         break;
  897.                     }
  898.                 }
  899.                 
  900.                 item.calendar = this_.superCalendar;
  901.                 var rid = item.recurrenceId;
  902.                 if (rid) {
  903.                     item.recurrenceInfo = null;
  904.                     if (LOG_LEVEL > 1) {
  905.                         log("exception item: " + item.title +
  906.                             "\nrid=" + rid.icalString +
  907.                             "\nitem.id=" + item.id, this_);
  908.                     }
  909.                     excItems.push(item);
  910.                 }
  911.                 else if (item.recurrenceInfo) {
  912.                     unexpandedItems.push(item);
  913.                     uid2parent[item.id] = item;
  914.                 }
  915.                 else if (maxResults == 0 || items.length < maxResults) {
  916.                     if (LOG_LEVEL > 2) {
  917.                         log("item: " + item.title + "\n" + item.icalString,
  918.                             this_);
  919.                     }
  920.                     if (!bLeaveMutable)
  921.                         item.makeImmutable();
  922.                     items.push(item);
  923.                 }
  924.             }
  925.         },
  926.         maxResults);
  927.     
  928.     // tag "exceptions", i.e. items with rid:
  929.     for each (var item in excItems) {
  930.         var parent = uid2parent[item.id];
  931.         if (parent) {
  932.             item.parentItem = parent;
  933.             var recStartDate = parent.recurrenceStartDate;
  934.             if (recStartDate && recStartDate.isDate && !item.recurrenceId.isDate) {
  935.                 // cs ought to return proper all-day RECURRENCE-ID!
  936.                 // get into startDate's timezone before cutting:
  937.                 var rid = item.recurrenceId.getInTimezone(recStartDate.timezone);
  938.                 rid.isDate = true;
  939.                 item.recurrenceId = rid;
  940.             }
  941.             item.makeImmutable();
  942.             parent.recurrenceInfo.modifyException(item);
  943.         }
  944.         else {
  945.             logError("parseItems(): no parent item for " + item.title +
  946.                      ", rid=" + item.recurrenceId.icalString +
  947.                      ", item.id=" + item.id, this);
  948.             // due to a server bug, in some scenarions the returned
  949.             // data is lacking the parent item, leave parentItem open then
  950.             if ((itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) == 0)
  951.                 item.recurrenceId = null;
  952.             if (!bLeaveMutable)
  953.                 item.makeImmutable();
  954.             items.push(item);
  955.         }
  956.     }
  957.     
  958.     if (itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) {
  959.         for each ( var item in unexpandedItems ) {
  960.             if (maxResults != 0 && items.length >= maxResults)
  961.                 break;
  962.             if (!bLeaveMutable)
  963.                 item.makeImmutable();
  964.             var occurrences = item.recurrenceInfo.getOccurrences(
  965.                 rangeStart, rangeEnd,
  966.                 maxResults == 0 ? 0 : maxResults - items.length,
  967.                 {} );
  968.             if (LOG_LEVEL > 1) {
  969.                 log("item: " + item.title + " has " +
  970.                     occurrences.length.toString() + " occurrences.", this);
  971.                 if (LOG_LEVEL > 2) {
  972.                     for each ( var occ in occurrences ) {
  973.                         log("item: " + occ.title + "\n" + occ.icalString, this);
  974.                     }
  975.                 }
  976.             }
  977.             // only proxies returned:
  978.             items = items.concat(occurrences);
  979.         }
  980.     }
  981.     else {
  982.         if (maxResults != 0 &&
  983.             (items.length + unexpandedItems.length) > maxResults) {
  984.             unexpandedItems.length = (maxResults - items.length);
  985.         }
  986.         if (!bLeaveMutable) {
  987.             for each ( var item in unexpandedItems ) {
  988.                 item.makeImmutable();
  989.             }
  990.         }
  991.         if (LOG_LEVEL > 2) {
  992.             for each ( var item in unexpandedItems ) {
  993.                 log("item: " + item.title + "\n" + item.icalString, this);
  994.             }
  995.         }
  996.         items = items.concat(unexpandedItems);
  997.     }
  998.     
  999.     if (LOG_LEVEL > 1)
  1000.         log("parseItems(): returning " + items.length + " items", this);
  1001.     return items;
  1002. };
  1003.  
  1004. calWcapCalendar.prototype.getItem =
  1005. function calWcapCalendar_getItem(id, listener)
  1006. {
  1007.     // xxx todo: test
  1008.     // xxx todo: howto detect whether to call
  1009.     //           fetchevents_by_id ot fetchtodos_by_id?
  1010.     //           currently drag/drop is implemented for events only,
  1011.     //           try events first, fallback to todos... in the future...
  1012.     
  1013.     var this_ = this;
  1014.     var request = new calWcapRequest(
  1015.         function getItem_resp(request, err, item) {
  1016.             if (listener) {
  1017.                 listener.onOperationComplete(
  1018.                     this_.superCalendar, getResultCode(err),
  1019.                     calIOperationListener.GET,
  1020.                     item.id, err ? err : item);
  1021.             }
  1022.             if (err)
  1023.                 this_.notifyError(err);
  1024.         },
  1025.         log("getItem() call: id=" + id, this));
  1026.     
  1027.     try {
  1028.         if (!id)
  1029.             throw new Components.Exception("no item id!");
  1030.         var params = "&relativealarm=1&compressed=1&recurring=1";
  1031.         params += "&emailorcalid=1&fmt-out=text%2Fcalendar&uid=";
  1032.         params += encodeURIComponent(id);
  1033.         
  1034.         function notifyResult(icalRootComp) {
  1035.             var items = this_.parseItems(icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
  1036.             if (items.length < 1)
  1037.                 throw new Components.Exception("no such item!");
  1038.             if (items.length > 1)
  1039.                 this_.notifyError("unexpected number of items: " + items.length);
  1040.             if (listener) {
  1041.                 listener.onGetResult(
  1042.                     this_.superCalendar, NS_OK,
  1043.                     calIItemBase, log("getItem(): success. id=" + id, this_),
  1044.                     items.length, items);
  1045.             }
  1046.             request.execRespFunc(null, items[0]);
  1047.         };
  1048.         // most common: try events first
  1049.         this.issueNetworkRequest(
  1050.             request,
  1051.             function fetchEventById_resp(err, icalRootComp) {
  1052.                 if (err) {
  1053.                     if (getResultCode(err) != calIWcapErrors.WCAP_FETCH_EVENTS_BY_ID_FAILED)
  1054.                         throw err;
  1055.                     // try todos:
  1056.                     this_.issueNetworkRequest(
  1057.                         request,
  1058.                         function fetchTodosById_resp(err, icalRootComp) {
  1059.                             if (err)
  1060.                                 throw err;
  1061.                             notifyResult(icalRootComp);
  1062.                         },
  1063.                         stringToIcal, "fetchtodos_by_id", params, calIWcapCalendar.AC_COMP_READ);
  1064.                 }
  1065.                 else {
  1066.                     notifyResult(icalRootComp);
  1067.                 }
  1068.             },
  1069.             stringToIcal, "fetchevents_by_id", params, calIWcapCalendar.AC_COMP_READ);
  1070.     }
  1071.     catch (exc) {
  1072.         request.execRespFunc(exc);
  1073.     }
  1074.     return request;
  1075. };
  1076.  
  1077. function getItemFilterParams(itemFilter)
  1078. {
  1079.     var params = "";
  1080.     switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) {
  1081.     case calICalendar.ITEM_FILTER_TYPE_TODO:
  1082.         params += "&component-type=todo"; break;
  1083.     case calICalendar.ITEM_FILTER_TYPE_EVENT:
  1084.         params += "&component-type=event"; break;
  1085.     }
  1086.     
  1087.     var compstate = "";
  1088. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REPLY_DECLINED)
  1089. //         compstate += ";REPLY-DECLINED";
  1090. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REPLY_ACCEPTED)
  1091. //         compstate += ";REPLY-ACCEPTED";
  1092. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_COMPLETED)
  1093. //         compstate += ";REQUEST-COMPLETED";
  1094.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_NEEDS_ACTION)
  1095.         compstate += ";REQUEST-NEEDS-ACTION";
  1096.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_NEEDSNOACTION)
  1097.         compstate += ";REQUEST-NEEDSNOACTION";
  1098. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_PENDING)
  1099. //         compstate += ";REQUEST-PENDING";
  1100. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_WAITFORREPLY)
  1101. //         compstate += ";REQUEST-WAITFORREPLY";
  1102.     if (compstate.length > 0)
  1103.         params += ("&compstate=" + compstate.substr(1));
  1104.     return params;
  1105. }
  1106.  
  1107. calWcapCalendar.prototype.getItems =
  1108. function calWcapCalendar_getItems(itemFilter, maxResults, rangeStart, rangeEnd, listener)
  1109. {
  1110.     // assure DATE-TIMEs:
  1111.     if (rangeStart && rangeStart.isDate) {
  1112.         rangeStart = rangeStart.clone();
  1113.         rangeStart.isDate = false;
  1114.     }
  1115.     if (rangeEnd && rangeEnd.isDate) {
  1116.         rangeEnd = rangeEnd.clone();
  1117.         rangeEnd.isDate = false;
  1118.     }
  1119.     var zRangeStart = getIcalUTC(rangeStart);
  1120.     var zRangeEnd = getIcalUTC(rangeEnd);
  1121.     
  1122.     var this_ = this;
  1123.     var request = new calWcapRequest(
  1124.         function getItems_resp(request, err, data) {
  1125.             log("getItems() complete: " + errorToString(err), this_);
  1126.             var rc = getResultCode(err);
  1127.             if (err) {
  1128.                 if (listener) {
  1129.                     listener.onOperationComplete(
  1130.                         this_.superCalendar, rc,
  1131.                         calIOperationListener.GET,
  1132.                         null, err);
  1133.                 }
  1134.                 this_.notifyError(err, request.suppressOnError);
  1135.             }
  1136.             else {
  1137.                 if (listener) {
  1138.                     listener.onOperationComplete(
  1139.                         this_.superCalendar, rc,
  1140.                         calIOperationListener.GET,
  1141.                         null, null);
  1142.                 }
  1143.             }
  1144.         },
  1145.         log("getItems():\n\titemFilter=0x" + itemFilter.toString(0x10) +
  1146.             ",\n\tmaxResults=" + maxResults +
  1147.             ",\n\trangeStart=" + zRangeStart +
  1148.             ",\n\trangeEnd=" + zRangeEnd, this));
  1149.     
  1150.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR)
  1151.         request.suppressOnError = true;
  1152.     
  1153.     if (this.aboutToBeUnregistered) {
  1154.         // limiting the amount of network traffic while unregistering
  1155.         log("being unregistered, no results.", this);
  1156.         request.execRespFunc(null, []);
  1157.         return request;
  1158.     }
  1159.     
  1160.     // m_cachedResults holds the last data revtrieval. This is expecially useful when
  1161.     // switching on multiple subcriptions: the composite calendar multiplexes getItems()
  1162.     // calls to all composited calendars over and over again, most often on the same
  1163.     // date range (as the user usually looks at the same view).
  1164.     // This will most likely vanish when a better caching is implemented in the views,
  1165.     // or WCAP local storage caching has sufficient performance.
  1166.     // The cached results will be invalidated after 2 minutes to reflect incoming invitations.
  1167.     if (CACHE_LAST_RESULTS > 0 && this.m_cachedResults) {
  1168.         for each (var entry in this.m_cachedResults) {
  1169.             if ((itemFilter == entry.itemFilter) &&
  1170.                 equalDatetimes(rangeStart, entry.rangeStart) &&
  1171.                 equalDatetimes(rangeEnd, entry.rangeEnd)) {
  1172.                 log("reusing last getItems() cached data.", this);
  1173.                 if (listener) {
  1174.                     listener.onGetResult(
  1175.                         this.superCalendar, NS_OK, calIItemBase,
  1176.                         "getItems()", entry.results.length, entry.results);
  1177.                 }
  1178.                 request.execRespFunc(null, entry.results);
  1179.                 return request;
  1180.             }
  1181.         }
  1182.     }
  1183.     
  1184.     try {
  1185.         var params = ("&relativealarm=1&compressed=1&recurring=1" +
  1186.                       "&emailorcalid=1&fmt-out=text%2Fcalendar");
  1187.         // setting component-type, compstate filters:
  1188.         params += getItemFilterParams(itemFilter);
  1189.         if (maxResults > 0)
  1190.             params += ("&maxResults=" + maxResults);
  1191.         params += ("&dtstart=" + zRangeStart);
  1192.         params += ("&dtend=" + zRangeEnd);
  1193.         
  1194.         this.issueNetworkRequest(
  1195.             request,
  1196.             function netResp(err, icalRootComp) {
  1197.                 if (err) {
  1198.                     if (getResultCode(err) == calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR) {
  1199.                         // try free-busy times:
  1200.                         if (listener &&
  1201.                             (itemFilter & calICalendar.ITEM_FILTER_TYPE_EVENT) &&
  1202.                             rangeStart && rangeEnd)
  1203.                         {
  1204.                             var freeBusyListener = { // calIGenericOperationListener:
  1205.                                 onResult: function freeBusyListener_onResult(request, result) {
  1206.                                     if (!request.success)
  1207.                                         throw request.status;
  1208.                                     var items = [];
  1209.                                     for each (var entry in result) {
  1210.                                         var item = new CalEvent();
  1211.                                         item.id = (g_busyPhantomItemUuidPrefix +
  1212.                                                    getIcalUTC(entry.interval.start));
  1213.                                         item.calendar = this_.superCalendar;
  1214.                                         item.title = g_busyItemTitle;
  1215.                                         item.startDate = entry.interval.start;
  1216.                                         item.endDate = entry.interval.end;
  1217.                                         item.makeImmutable();
  1218.                                         items.push(item);
  1219.                                     }
  1220.                                     listener.onGetResult(
  1221.                                         this_.superCalendar, NS_OK, calIItemBase,
  1222.                                         "getItems()/free-busy", items.length, items);
  1223.                                 }
  1224.                             };
  1225.                             request.attachSubRequest(
  1226.                                 this_.session.getFreeBusyIntervals(
  1227.                                     this_.calId, rangeStart, rangeEnd, calIFreeBusyInterval.BUSY_ALL,
  1228.                                     freeBusyListener));
  1229.                         }
  1230.                     }
  1231.                     else
  1232.                         throw err;
  1233.                 }
  1234.                 else if (listener) {
  1235.                     var items = this_.parseItems(
  1236.                         icalRootComp, itemFilter, maxResults,
  1237.                         rangeStart, rangeEnd);
  1238.                     
  1239.                     if (CACHE_LAST_RESULTS > 0) {
  1240.                         // auto invalidate after X minutes:
  1241.                         if (!this_.m_cachedResultsTimer) {
  1242.                             var callback = {
  1243.                                 notify: function notify(timer) {
  1244.                                     if (!this_.m_cachedResults)
  1245.                                         return;
  1246.                                     var now = (new Date()).getTime();
  1247.                                     // sort out old entries:
  1248.                                     var entries = [];
  1249.                                     for (var i = 0; i < this_.m_cachedResults.length; ++i) {
  1250.                                         var entry = this_.m_cachedResults[i];
  1251.                                         if ((now - entry.stamp) <
  1252.                                             (CACHE_LAST_RESULTS_INVALIDATE * 1000)) {
  1253.                                             entries.push(entry);
  1254.                                         }
  1255.                                         else {
  1256.                                             log("invalidating cached entry:\n\trangeStart=" +
  1257.                                                 getIcalUTC(entry.rangeStart) + "\n\trangeEnd=" +
  1258.                                                 getIcalUTC(entry.rangeEnd), this_);
  1259.                                         }
  1260.                                     }
  1261.                                     this_.m_cachedResults = entries;
  1262.                                 }
  1263.                             };
  1264.                             // sort out freq:
  1265.                             var freq = Math.min(
  1266.                                 20, // default: 20secs
  1267.                                 Math.max(1, CACHE_LAST_RESULTS_INVALIDATE));
  1268.                             log("cached results sort out timer freq: " + freq, this_);
  1269.                             this_.m_cachedResultsTimer = new Timer();
  1270.                             this_.m_cachedResultsTimer.initWithCallback(
  1271.                                 callback, freq * 1000,
  1272.                                 Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  1273.                         }
  1274.                         if (!this_.m_cachedResults)
  1275.                             this_.m_cachedResults = [];
  1276.                         var entry = {
  1277.                             stamp: (new Date()).getTime(),
  1278.                             itemFilter: itemFilter,
  1279.                             rangeStart: (rangeStart ? rangeStart.clone() : null),
  1280.                             rangeEnd: (rangeEnd ? rangeEnd.clone() : null),
  1281.                             results: items
  1282.                         };
  1283.                         this_.m_cachedResults.unshift(entry);
  1284.                         if (this_.m_cachedResults.length > CACHE_LAST_RESULTS)
  1285.                             this_.m_cachedResults.length = CACHE_LAST_RESULTS;
  1286.                     }
  1287.                     
  1288.                     listener.onGetResult(this_.superCalendar, NS_OK, calIItemBase,
  1289.                                          "getItems()", items.length, items);
  1290.                 }
  1291.             },
  1292.             stringToIcal,
  1293.             (itemFilter & calIWcapCalendar.ITEM_FILTER_BY_ALARM_RANGE)
  1294.             ? "fetchcomponents_by_alarmrange" : "fetchcomponents_by_range",
  1295.             params, calIWcapCalendar.AC_COMP_READ);
  1296.     }
  1297.     catch (exc) {
  1298.         request.execRespFunc(exc);
  1299.     }
  1300.     return request;
  1301. };
  1302.  
  1303. // function calWcapSyncOperationListener() {
  1304. //     this.superClass(respFunc);
  1305. //     this.wrappedJSObject = this;
  1306. // }
  1307. // subClass(calWcapSyncOperationListener, calWcapRequest);
  1308.  
  1309. // calWcapSyncOperationListener.prototype.QueryInterface =
  1310. // function calWcapSyncOperationListener_QueryInterface(iid) {
  1311. //     // xxx todo:
  1312. //     const m_ifaces = [ Components.interfaces.nsISupports,
  1313. //                        Components.interfaces.calIOperationListener,
  1314. //                        Components.interfaces.calIWcapRequest ];
  1315. //     qiface(m_ifaces, iid);
  1316. //     return this;
  1317. // };
  1318.  
  1319. // // calIOperationListener:
  1320. // calWcapSyncOperationListener.prototype.onOperationComplete =
  1321. // function calWcapSyncOperationListener_onOperationComplete(
  1322. //     calendar, status, opType, id, detail)
  1323. // {
  1324. //     if (status != NS_OK) {
  1325. //         this.
  1326. //             this.m_syncState.abort( detail );
  1327. //     }
  1328. //     else if (this.m_opType != opType) {
  1329. //         this.m_syncState.abort(
  1330. //             new Components.Exception("unexpected operation type: " +
  1331. //                                      opType) );
  1332. //     }
  1333. //     this.m_syncState.release();
  1334. // };
  1335.  
  1336. // calWcapSyncOperationListener.prototype.onGetResult =
  1337. // function calWcapSyncOperationListener_onGetResult(
  1338. //     calendar, status, itemType, detail, count, items)
  1339. // {
  1340. //     this.m_syncState.abort(
  1341. //         new Components.Exception("unexpected onGetResult()!") );
  1342. // };
  1343.  
  1344. // calWcapCalendar.prototype.syncChangesTo_resp = function(
  1345. //     wcapResponse, syncState, listener, func )
  1346. // {
  1347. //     try {
  1348. //         var icalRootComp = wcapResponse.data; // first statement, may throw
  1349. //         var items = this.parseItems_(
  1350. //             function(items) { items.forEach(func) },
  1351. //             icalRootComp,
  1352. //             calICalendar.ITEM_FILTER_ALL_ITEMS,
  1353. //             0, null, null );
  1354. //     }
  1355. //     catch (exc) {
  1356. //         syncState.abort( exc );
  1357. //     }
  1358. //     syncState.release();
  1359. // };
  1360.  
  1361. calWcapCalendar.prototype.syncChangesTo =
  1362. function calWcapCalendar_syncChangesTo(destCal, itemFilter, dtFrom_, listener)
  1363. {
  1364.     // xxx todo: move to Thomas
  1365.     // do NOT puke up error box every three minutes!
  1366.     itemFilter |= calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR;
  1367.     
  1368.     var now = getTime(); // new stamp for this sync
  1369.     var this_ = this;
  1370.     var request_ = new calWcapRequest(
  1371.         function syncChangesTo_resp(request, err) {
  1372.             if (err) {
  1373.                 log("SYNC failed!", this_);
  1374.                 if (listener) {
  1375.                     listener.onOperationComplete(
  1376.                         this_.superCalendar, getResultCode(err),
  1377.                         calIWcapCalendar.SYNC, null, err);
  1378.                 }
  1379.                 this_.notifyError(err, request.suppressOnError);
  1380.             }
  1381.             else {
  1382.                 log("SYNC succeeded.", this_);
  1383.                 if (listener) {
  1384.                     listener.onOperationComplete(
  1385.                         this_.superCalendar, NS_OK,
  1386.                         calIWcapCalendar.SYNC, null, now);
  1387.                 }
  1388.             }
  1389.         },
  1390.         log("syncChangesTo():\n\titemFilter=0x" + itemFilter.toString(0x10) +
  1391.             "\n\tdtFrom_=" + getIcalUTC(dtFrom_), this));
  1392.     
  1393.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR)
  1394.         request_.suppressOnError = true;
  1395.     
  1396.     try {
  1397.         var dtFrom = dtFrom_;
  1398.         if (dtFrom) {
  1399.             dtFrom = dtFrom.clone();
  1400.             // assure DATE-TIME:
  1401.             if (dtFrom.isDate)
  1402.                 dtFrom.isDate = false;
  1403.             dtFrom = this.session.getServerTime(dtFrom);
  1404.         }
  1405.         var zdtFrom = getIcalUTC(dtFrom);
  1406.         
  1407.         var calObserver = null;
  1408.         if (listener) {
  1409.             try {
  1410.                 calObserver = listener.QueryInterface(Components.interfaces.calIObserver);
  1411.             }
  1412.             catch (exc) {
  1413.             }
  1414.         }
  1415.         
  1416.         var request = new calWcapRequest(
  1417.             function netFinishedRespFunc(err, data) {
  1418.                 var modifiedIds = {};
  1419.                 for each (var item in request.m_modifiedItems) {
  1420.                     var dtCreated = item.getProperty("CREATED");
  1421.                     var bAdd = (!dtCreated || !dtFrom ||
  1422.                                 dtCreated.compare(dtFrom) >= 0);
  1423.                     modifiedIds[item.id] = true;
  1424.                     if (bAdd) {
  1425.                         // xxx todo: verify whether exceptions
  1426.                         //           have been written
  1427.                         log("syncChangesTo(): new item " + item.id, this_);
  1428.                         if (destCal) {
  1429. //                                 destCal.addItem(item, addItemListener);
  1430.                         }
  1431.                         if (calObserver)
  1432.                             calObserver.onAddItem(item);
  1433.                     }
  1434.                     else {
  1435.                         log("syncChangesTo(): modified item " + item.id, this_);
  1436.                         if (destCal) {
  1437. //                             destCal.modifyItem(item, null, modifyItemListener);
  1438.                         }
  1439.                         if (calObserver)
  1440.                             calObserver.onModifyItem(item, null);
  1441.                     }
  1442.                 }
  1443.                 for each (var item in request.m_deletedItems) {
  1444.                     // don't delete anything that has been touched by lastmods:
  1445.                     if (modifiedIds[item.id])
  1446.                         log("syncChangesTo(): skipping deletion of " + item.id, this_);
  1447.                     else if (isParent(item)) {
  1448.                         log("syncChangesTo(): deleted item " + item.id, this_);
  1449.                         if (destCal) {
  1450. //                             destCal.deleteItem(item, deleteItemListener);
  1451.                         }
  1452.                         if (calObserver)
  1453.                             calObserver.onDeleteItem(item);
  1454.                     }
  1455.                     else { // modify parent instead of
  1456.                            // straight-forward deleteItem(). WTF.
  1457.                         var parent = item.parentItem.clone();
  1458.                         parent.recurrenceInfo.removeOccurrenceAt(item.recurrenceId);
  1459.                         log("syncChangesTo(): modified parent "+ parent.id, this_);
  1460.                         if (destCal) {
  1461. //                             destCal.modifyItem(parent, item, deleteItemListener);
  1462.                         }
  1463.                         if (calObserver)
  1464.                             calObserver.onModifyItem(parent, item);
  1465.                     }
  1466.                 }
  1467.             }, "syncChangesTo() netFinishedRespFunc");
  1468.         request_.attachSubRequest(request);
  1469.         
  1470.         var params = ("&relativealarm=1&compressed=1&recurring=1" +
  1471.                       "&emailorcalid=1&fmt-out=text%2Fcalendar");
  1472.         params += ("&dtstart=" + zdtFrom);
  1473.         params += ("&dtend=" + getIcalUTC(this.session.getServerTime(now)));
  1474.         
  1475.         log("syncChangesTo(): getting last modifications...", this);
  1476.         this.issueNetworkRequest(
  1477.             request,
  1478.             function modifiedNetResp(err, icalRootComp) {
  1479.                 if (err)
  1480.                     throw err;
  1481.                 request.m_modifiedItems = this_.parseItems(
  1482.                     icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
  1483.             },
  1484.             stringToIcal, "fetchcomponents_by_lastmod",
  1485.             params + getItemFilterParams(itemFilter),
  1486.             calIWcapCalendar.AC_COMP_READ);        
  1487.         
  1488.         log("syncChangesTo(): getting deleted items...", this);
  1489.         this.issueNetworkRequest(
  1490.             request,
  1491.             function modifiedNetResp(err, icalRootComp) {
  1492.                 if (err)
  1493.                     throw err;
  1494.                 request.m_deletedItems = this_.parseItems(
  1495.                     icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
  1496.             },
  1497.             stringToIcal, "fetch_deletedcomponents",
  1498.             params + getItemFilterParams(itemFilter & // only component types
  1499.                                          calICalendar.ITEM_FILTER_TYPE_ALL),
  1500.             calIWcapCalendar.AC_COMP_READ);
  1501.     }
  1502.     catch (exc) {
  1503.         request_.execRespFunc(exc);
  1504.     }
  1505.     return request_;
  1506. };
  1507.  
  1508.